-- ===========================
-- Test the MEC algorithm.
-- ===========================
dofile("MEC.lua")

local maxPoints = 10
math.randomseed(1234)

-- Test the Point class
local function pointTest()
	print("===========================")
	print(". . . . POINT TEST . . . . ")
	print("===========================")
	print("Creating new points:")
	local points = {}
	for i = 1, maxPoints do
		table.insert(points, 
					 Point:New({x = math.random(), y = math.random()})
					 )
		print("point[" .. i .. "] = " .. points[i]:ToString())
	end
	print()
	
	print("===========================")
	print("CloseTo:")
	local eps = 1e-9
	local p1 = Point:New({x = math.random(), y = math.random()})
	local p2 = Point:New({x = p1.x + eps, y = p1.y - eps})
	print("p1 = " .. p1:ToString())
	print("p2 = " .. p2:ToString())
	print("Point.CloseTo(p1.x, p2.x, " .. tostring(eps) .. "): " .. tostring(Point.CloseTo(p1.x, p2.x, eps)))
	print("Point.CloseTo(p1.y, p2.y, " .. tostring(eps) .. "): " .. tostring(Point.CloseTo(p1.y, p2.y, eps)))
	print()
	
	print("===========================")
	print("Not CloseTo:")
	eps = 1e-9
	local offset = 1
	p1 = Point:New({x = math.random(), y = math.random()})
	p2 = Point:New({x = p1.x + offset, y = p1.y - offset})
	print("p1 = " .. p1:ToString())
	print("p2 = " .. p2:ToString())
	print("Point.CloseTo(p1.x, p2.x, " .. tostring(eps) .. "): " .. tostring(Point.CloseTo(p1.x, p2.x, eps)))
	print("Point.CloseTo(p1.y, p2.y, " .. tostring(eps) .. "): " .. tostring(Point.CloseTo(p1.y, p2.y, eps)))
	print()
	
	print("===========================")
	print("Add: ")
	local x1, y1, x2, y2 = math.random(), math.random(), math.random(), math.random()
	local x3 = x1 + x2
	local y3 = y1 + y2
	p1 = Point:New({x = x1, y = y1})
	p2 = Point:New({x = x2, y = y2})
	p3 = p1 + p2
	print("p1 = " .. p1:ToString())
	print("p2 = " .. p2:ToString())
	print("p3 = " .. p3:ToString())
	print("Success = " .. tostring(p3.x == x3 and p3.y == y3))
	print()
	
	print("===========================")
	print("Sub: ")
	x1, y1, x2, y2 = math.random(), math.random(), math.random(), math.random()
	x3 = x1 - x2
	y3 = y1 - y2
	p1 = Point:New({x = x1, y = y1})
	p2 = Point:New({x = x2, y = y2})
	p3 = p1 - p2
	print("p1 = " .. p1:ToString())
	print("p2 = " .. p2:ToString())
	print("p3 = " .. p3:ToString())
	print("Success = " .. tostring(p3.x == x3 and p3.y == y3))
	print()
	
	print("===========================")
	print("Equals: ")
	x1, y1 = math.random(), math.random()
	p1 = Point:New({x = x1, y = y1})
	p2 = Point:New({x = x1, y = y1})
	print("p1 = " .. p1:ToString())
	print("p2 = " .. p2:ToString())
	print("(p1 == p2) = " .. tostring(p1 == p2))
	print()
	
	print("===========================")
	print("Scale: ")
	x1, y1 = math.random(), math.random()
	local scale = math.random()
	x2, y2 = x1 * scale, y1 * scale
	p1 = Point:New({x = x1, y = y1})
	p2 = p1:Scale(scale)
	print("p1 = " .. p1:ToString())
	print("p2 = " .. p2:ToString())
	print("scale = " .. scale)
	print("Success = " .. tostring(p2.x == x2 and p2.y == y2))
	print()
	
	print("===========================")
	print("NormalLeft: ")
	x1, y1 = math.random(), math.random()
	x2, y2 = y1, -x1
	p1 = Point:New({x = x1, y = y1})
	p2 = p1:NormalLeft()
	print("p1 = " .. p1:ToString())
	print("p2 = " .. p2:ToString())
	print("Success = " .. tostring(p2.x == x2 and p2.y == y2))
	print()
	
	print("===========================")
	print("Center: ")
	x1, y1 = math.random(), math.random()
	x3, y3 = 0, 0
	p1 = Point:New({x = x1, y = y1})
	p2 = p1:Scale(-1)
	p3 = p1:Center(p2)
	print("p1 = " .. p1:ToString())
	print("p2 = " .. p2:ToString())
	print("p3 = " .. p3:ToString())
	print("Success = " .. tostring(p3.x == x3 and p3.y == y3))
	print()
	
	print("===========================")
	print("Distance: ")
	x1, y1 = math.random(), math.random()
	x2, y2 = math.random(), math.random()
	p1 = Point:New({x = x1, y = y1})
	p2 = Point:New({x = x2, y = y2})
	local dx, dy = x1 - x2, y1 - y2
	local distance = math.sqrt(dx * dx + dy * dy)
	print("p1 = " .. p1:ToString())
	print("p2 = " .. p2:ToString())
	print("distance = " .. p1:Distance(p2))
	print("Success = " .. tostring(distance == p1:Distance(p2)))
	print()
	
	print("===========================")
	print("Direction (left): ")
	x1, y1 = 0, math.random()
	x2, y2 = x1, -y1
	x3, y3 = -1, y1
	p1 = Point:New({x = x1, y = y1})
	p2 = Point:New({x = x2, y = y2})
	p3 = Point:New({x = x3, y = y3})
	print("p1 = " .. p1:ToString())
	print("p2 = " .. p2:ToString())
	print("p3 = " .. p3:ToString())
	print("Direction = " .. tostring(Point.Direction(p1, p2, p3)))
	print("Success = " .. tostring(Point.Direction(p1, p2, p3) > 0))
	print()
	
	print("===========================")
	print("Direction (right): ")
	x1, y1 = 0, math.random()
	x2, y2 = x1, -y1
	x3, y3 = 1, y1
	p1 = Point:New({x = x1, y = y1})
	p2 = Point:New({x = x2, y = y2})
	p3 = Point:New({x = x3, y = y3})
	print("p1 = " .. p1:ToString())
	print("p2 = " .. p2:ToString())
	print("p3 = " .. p3:ToString())
	print("Direction = " .. tostring(Point.Direction(p1, p2, p3)))
	print("Success = " .. tostring(Point.Direction(p1, p2, p3) < 0))
	print()

	print("===========================")
	print("Direction (on): ")
	x1, y1 = 1, 1
	x2, y2 = 1, -1
	x3, y3 = 1, 0
	p1 = Point:New({x = x1, y = y1})
	p2 = Point:New({x = x2, y = y2})
	p3 = Point:New({x = x3, y = y3})
	print("p1 = " .. p1:ToString())
	print("p2 = " .. p2:ToString())
	print("p3 = " .. p3:ToString())
	print("Direction = " .. tostring(Point.Direction(p1, p2, p3)))
	print("Success = " .. tostring(Point.Direction(p1, p2, p3) == 0))
	print()
end

-- Test the circle class
local function circleTest()
	print("===========================")
	print("o o o o CIRCLE TEST o o o o")
	print("===========================")
	print("Create Circle:")
	local c = Circle:New()
	local p = Point:New({x = math.random(), y = math.random()})
	c.center = p
	c.radius = math.random()
	print("Circle: " .. c:ToString())
	print()
	
	print("===========================")
	print("Contains (obvious and border):")
	local x1, y1 = math.random(), math.random()
	c = Circle:New()
	local p1 = Point:New({x = x1, y = y1})
	local p2 = p1 + p1
	local p3 = p1 + p1:Scale(2)
	local p4 = p1 + p1:Scale(3)
	c.center = p1
	c.radius = p1:Distance(p3)
	print("p1 = " .. p1:ToString())
	print("p2 = " .. p2:ToString())
	print("p3 = " .. p3:ToString())
	print("p4 = " .. p4:ToString())
	print("Circle: " .. c:ToString())
	print("Circle contains p1: " .. tostring(c:Contains(p1)))
	print("Circle contains p2: " .. tostring(c:Contains(p2)))
	print("Circle contains p3: " .. tostring(c:Contains(p3)))
	print("Circle does not contain p4: " .. tostring(not c:Contains(p4)))
	print()
end

-- Test the array utility functions
local function arrayUtilsTest()
	print("===========================")
	print("~ ~ ~ ~ ARRAY UTILS ~ ~ ~ ~")
	print("===========================")
	print("CopyArray (true): ")
	local points = {}
	for i = 1, maxPoints do
		table.insert(points, Point:New({x = math.random(), y = math.random()}))
	end
	local pointsCopy = CopyArray(points)
	local allTheSame = true
	for i,p in ipairs(pointsCopy) do
		if points[i] ~= p then
			allTheSame = false
		end 
	end
	print("Success: " .. tostring(allTheSame))
	print()
	
	print("===========================")
	print("CopyArray (false): ")
	pointsCopy[1] = pointsCopy[#pointsCopy]
	local allTheSame = true
	for i,p in ipairs(pointsCopy) do
		if points[i] ~= p then
			allTheSame = false
		end 
	end
	print("Success: " .. tostring(not allTheSame))
	print()
	
	print("===========================")
	print("DeleteAt (false): ")
	points = {}
	for i = 1, maxPoints do
		table.insert(points, Point:New({x = math.random(), y = math.random()}))
		--print("point[" .. i .. "] = " .. points[i]:ToString())
	end
	local deleteIndex = math.random(#points)
	print("#points: " .. tostring(#points))
	print("deleteIndex: " .. tostring(deleteIndex))
	local pointsDeleteAt = DeleteAt(points, deleteIndex)
	for i,p in ipairs(pointsDeleteAt) do
		print("pointsDeleteAt[" .. i .. "] = " .. p:ToString())
	end
	print("#pointsDeleteAt: " .. tostring(#pointsDeleteAt))
	print("Success: " .. tostring(
							pointsDeleteAt[deleteIndex] ~= points[deleteIndex] 
							and #pointsDeleteAt == (#points - 1)
							)
		)
	print()
	
	print("===========================")
	print("JoinUniqueArrayElements (same array): ")
	local points1 = {}
	--print("points1: ")
	for i = 1, maxPoints do
		table.insert(points1, Point:New({x = math.random(), y = math.random()}))
		--print("point1[" .. i .. "] = " .. points1[i]:ToString())
	end
	local points2 = {}
	--print("points2: ")
	for i,p in ipairs(points1) do
		points2[i] = p
		--print("points2[" .. i .. "] = " .. points2[i]:ToString())
	end
	local uniquePoints = JoinUniqueArrayElements(points1, points2)
	local duplicates = {}
	local success = true
	for i,p in ipairs(uniquePoints) do
		-- Make sure there are no duplicate points.
		if duplicates["x" .. p.x .. "y" .. p.y] then
			success = false
			break
		else
			duplicates["x" .. p.x .. "y" .. p.y] = true
		end
		--print("uniquePoints[" .. i .. "] = " .. uniquePoints[i]:ToString())
	end
	print("Success: " .. tostring(success))
	print()
	
	print("===========================")
	print("JoinUniqueArrayElements (one array is a subset of the other): ")
	local points1 = {}
	--print("points1: ")
	for i = 1, maxPoints do
		table.insert(points1, Point:New({x = math.random(), y = math.random()}))
		--print("point1[" .. i .. "] = " .. points1[i]:ToString())
	end
	local points2 = {}
	--print("points2: ")
	for i,p in ipairs(points1) do
		--points2[i] = p
		table.insert(points2, p)
		table.insert(points2, Point:New({x = math.random(), y = math.random()}))
	end
	for i,p in ipairs(points2) do
		--print("points2[" .. i .. "] = " .. points2[i]:ToString())
	end
	local uniquePoints = JoinUniqueArrayElements(points1, points2)
	local duplicates = {}
	local success = true
	for i,p in ipairs(uniquePoints) do
		-- Make sure there are no duplicate points.
		if duplicates["x" .. p.x .. "y" .. p.y] then
			success = false
			break
		else
			duplicates["x" .. p.x .. "y" .. p.y] = true
		end
		--print("uniquePoints[" .. i .. "] = " .. uniquePoints[i]:ToString())
	end
	print("Success: " .. tostring(success))
	print()
	
	print("===========================")
	print("JoinUniqueArrayElements (all different): ")
	local points1 = {}
	--print("points1: ")
	for i = 1, maxPoints do
		table.insert(points1, Point:New({x = math.random(), y = math.random()}))
		--print("point1[" .. i .. "] = " .. points1[i]:ToString())
	end
	local points2 = {}
	--print("points2: ")
	for i = 1, maxPoints do
		table.insert(points2, Point:New({x = math.random(), y = math.random()}))
		--print("points2[" .. i .. "] = " .. points2[i]:ToString())
	end
	local uniquePoints = JoinUniqueArrayElements(points1, points2)
	local duplicates = {}
	local success = true
	for i,p in ipairs(uniquePoints) do
		-- Make sure there are no duplicate points.
		if duplicates["x" .. p.x .. "y" .. p.y] then
			success = false
			break
		else
			duplicates["x" .. p.x .. "y" .. p.y] = true
		end
		--print("uniquePoints[" .. i .. "] = " .. uniquePoints[i]:ToString())
	end
	print("Success: " .. tostring(success))
	print()
end

-- Test the Minimum Enclosing Circle algorithm
local function mecTest()
	print("===========================")
	print("@ @ @ @ MEC TEST @ @ @ @")
	print("===========================")
	local points = {}
	for i = 1, maxPoints do
		table.insert(points, Point:New({x = math.random(), y = math.random()}))
		print("points[" .. i .. "] = " .. points[i]:ToString())
	end
	local mec = MEC:New(points)
	local circle = mec:Compute()
	
	local success = true
	for i, p in ipairs(points) do
		if not circle:Contains(p) then
			success = false
			break
		end
	end
	print("Success: " .. tostring(success))
end

local function main()
	pointTest()
	circleTest()
	arrayUtilsTest()
	mecTest()
end

main()